# SPDX-License-Identifier: Apache-2.0

import enum

from uuid import UUID

from sqlalchemy import Enum, ForeignKey, Index, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship

from warehouse import db
from warehouse.i18n import localize as _
from warehouse.organizations.models import (
    Organization,
    OrganizationStripeCustomer,
    OrganizationStripeSubscription,
)
from warehouse.utils.attrs import make_repr
from warehouse.utils.db.types import bool_true
from warehouse.utils.enum import StrLabelEnum


class StripeSubscriptionStatus(StrLabelEnum):
    # Name = "value", _("Label")
    Active = "active", _("Active")
    PastDue = "past_due", _("Past Due")
    Unpaid = "unpaid", _("Unpaid")
    Canceled = "canceled", _("Canceled")
    Incomplete = "incomplete", _("Incomplete")
    IncompleteExpired = "incomplete_expired", _("Incomplete Expired")
    Trialing = "trialing", _("Trialing")

    @classmethod
    def has_value(cls, value):
        return value in {item.value for item in StripeSubscriptionStatus}


class StripeSubscriptionPriceInterval(str, enum.Enum):
    Month = "month"
    Year = "year"
    Week = "week"
    Day = "day"


class StripeCustomer(db.Model):
    __tablename__ = "stripe_customers"

    __repr__ = make_repr("customer_id", "billing_email")

    customer_id: Mapped[str] = mapped_column(
        unique=True
    )  # generated by Payment Service Provider
    billing_email: Mapped[str | None]

    organization: Mapped["Organization"] = relationship(
        secondary=OrganizationStripeCustomer.__table__,
        back_populates="customer",
        uselist=False,
        viewonly=True,
    )
    subscriptions: Mapped[list["StripeSubscription"]] = relationship(lazy=False)


class StripeSubscription(db.Model):
    __tablename__ = "stripe_subscriptions"
    __table_args__ = (
        Index("stripe_subscriptions_stripe_customer_id_idx", "stripe_customer_id"),
        Index("stripe_subscriptions_subscription_id_idx", "subscription_id"),
        UniqueConstraint(
            "stripe_customer_id",
            "subscription_id",
            name="_stripe_subscription_customer_subscription_uc",
        ),
    )

    __repr__ = make_repr("subscription_id", "stripe_customer_id")

    stripe_customer_id: Mapped[UUID] = mapped_column(
        PG_UUID,
        ForeignKey("stripe_customers.id", onupdate="CASCADE", ondelete="CASCADE"),
    )
    subscription_id: Mapped[str]  # generated by Payment Service Provider
    subscription_price_id: Mapped[UUID] = mapped_column(
        PG_UUID,
        ForeignKey(
            "stripe_subscription_prices.id", onupdate="CASCADE", ondelete="CASCADE"
        ),
    )
    status: Mapped["StripeSubscriptionStatus"] = mapped_column(
        Enum(StripeSubscriptionStatus, values_callable=lambda x: [e.value for e in x]),
    )

    subscription_price: Mapped["StripeSubscriptionPrice"] = relationship(lazy=False)
    subscription_item: Mapped["StripeSubscriptionItem"] = relationship(
        back_populates="subscription",
        lazy=False,
        uselist=False,
    )
    organization: Mapped["Organization"] = relationship(
        secondary=OrganizationStripeSubscription.__table__,
        back_populates="subscriptions",
        uselist=False,
        viewonly=True,
    )
    customer: Mapped["StripeCustomer"] = relationship(
        back_populates="subscriptions",
        lazy=False,
        uselist=False,
    )

    @property
    def is_restricted(self):
        return self.status not in [
            StripeSubscriptionStatus.Active.value,
            StripeSubscriptionStatus.Trialing.value,
        ]

    @property
    def is_manageable(self):
        return self.status not in [
            StripeSubscriptionStatus.Canceled.value,
        ]


class StripeSubscriptionProduct(db.Model):
    __tablename__ = "stripe_subscription_products"

    __repr__ = make_repr("product_name")

    product_id: Mapped[str | None]  # generated by Payment Service Provider
    product_name: Mapped[str]
    description: Mapped[str]
    is_active: Mapped[bool_true]
    tax_code: Mapped[str | None]  # https://stripe.com/docs/tax/tax-categories


class StripeSubscriptionPrice(db.Model):
    __tablename__ = "stripe_subscription_prices"

    __repr__ = make_repr("price_id", "unit_amount", "recurring")

    price_id: Mapped[str | None]  # generated by Payment Service Provider
    currency: Mapped[str]  # https://stripe.com/docs/currencies
    subscription_product_id: Mapped[UUID] = mapped_column(
        PG_UUID,
        ForeignKey(
            "stripe_subscription_products.id", onupdate="CASCADE", ondelete="CASCADE"
        ),
    )
    unit_amount: Mapped[int]  # positive integer in cents
    is_active: Mapped[bool_true]
    recurring: Mapped[enum.Enum] = mapped_column(
        Enum(
            StripeSubscriptionPriceInterval,
            values_callable=lambda x: [e.value for e in x],
        ),
    )
    tax_behavior: Mapped[str | None]  # TODO: Enum? inclusive, exclusive, unspecified

    subscription_product: Mapped["StripeSubscriptionProduct"] = relationship(lazy=False)


class StripeSubscriptionItem(db.Model):
    __tablename__ = "stripe_subscription_items"

    __repr__ = make_repr(
        "subscription_item_id", "subscription_id", "subscription_price_id", "quantity"
    )

    subscription_item_id: Mapped[str | None]  # generated by Payment Service Provider
    subscription_id: Mapped[UUID] = mapped_column(
        PG_UUID,
        ForeignKey("stripe_subscriptions.id", onupdate="CASCADE", ondelete="CASCADE"),
    )
    subscription_price_id: Mapped[UUID] = mapped_column(
        PG_UUID,
        ForeignKey(
            "stripe_subscription_prices.id", onupdate="CASCADE", ondelete="CASCADE"
        ),
    )
    quantity: Mapped[int]  # positive integer or zero

    subscription: Mapped["StripeSubscription"] = relationship(
        lazy=False, back_populates="subscription_item"
    )
    subscription_price: Mapped["StripeSubscriptionPrice"] = relationship(lazy=False)
